﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FUSE.AzureConfig.Tasks
{
    public class SpecProcessor
    {
        public AzureConfig Process(AzureConfigSpec spec)
        {
            var roles = new Dictionary<string, AzureRoleDetails>();
            var envs = new Dictionary<string, AzureEnvironment>();
            foreach (var e in spec.Environments)
            {
                ProcessEnv(e.Key, spec, roles, envs);
            }
            foreach (var r in envs.Values.SelectMany(e => e.Roles.Values).GroupBy(r => r.RoleName))
            {
                var currentSettings = r.SelectMany(x => x.ConfigSettings.Settings.Keys);
                r.ToList().ForEach(x => x.Details.ConfigurationSettingKeys.UnionWith(currentSettings));
            }
            foreach (var r in envs.Values.SelectMany(e => e.Roles.Values))
            {
                r.ConfigSettings.AddMissing(r.Details.ConfigurationSettingKeys);
            }
            // Remove "parent" environments with no actual instances or with an empty profile name, and key off of profile name.
            envs = envs.Where(e => e.Value.Roles.Any(x => x.Value.Instances > 0) && !string.IsNullOrEmpty(e.Value.EnvName))
                .ToDictionary(x => x.Value.EnvName, x => x.Value);
            return new AzureConfig(spec.Name, roles, envs);
        }

        private AzureEnvironment ProcessEnv(string envName, 
            AzureConfigSpec spec,
            Dictionary<string, AzureRoleDetails> roles,
            Dictionary<string, AzureEnvironment> envs)
        {
            AzureEnvironment env;
            if (envs.TryGetValue(envName, out env))
                return env;

            var curSpec = spec.Environments.Single(s => s.Key == envName).Value;
            if (string.IsNullOrEmpty(curSpec.Parent))
            {
                env = Ensure(envs, envName, n => new AzureEnvironment(curSpec.ProfileName));
            }
            else
            {
                env = Ensure(envs, envName, n =>
                    AzureEnvironment.FromParent(curSpec.ProfileName, ProcessEnv(curSpec.Parent, spec, roles, envs)));
            }
            if (curSpec.Roles != null)
            {
                foreach (var r in curSpec.Roles)
                {
                    var role = Ensure(roles, r.Key, n => new AzureRoleDetails(n));
                    var roleEnv = Ensure(env.Roles, r.Key, n => new AzureRoleEnvironmentDetails(role));
                    roleEnv.Instances = r.Value.Instances;
                }
            }
            foreach (var s in curSpec.ConfigSettings)
            {
                var inRoles = s.Key.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
                foreach (var r in inRoles)
                {
                    var role = Ensure(roles, r, n => new AzureRoleDetails(n));
                    var roleEnv = Ensure(env.Roles, r, n => new AzureRoleEnvironmentDetails(role));
                    roleEnv.ConfigSettings.AddSettings(s.Value);
                }
            }
            foreach (var c in curSpec.Certificates)
            {
                var inRoles = c.Key.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
                foreach (var r in inRoles)
                {
                    var role = Ensure(roles, r, n => new AzureRoleDetails(n));
                    var roleEnv = Ensure(env.Roles, r, n => new AzureRoleEnvironmentDetails(role));
                    foreach (var cert in c.Value)
                    {
                        roleEnv.Certificates[cert.Key] = new AzureCertificate() { 
                            Thumbprint = cert.Value.Thumbprint, ThumbprintAlgorithm = cert.Value.ThumbprintAlgorithm };
                    }
                }
            }
            return env;
        }

        private static T Ensure<T>(IDictionary<string, T> dict, string key, Func<string, T> genNew)
        {
            T val;
            if (!dict.TryGetValue(key, out val))
            {
                val = genNew(key);
                dict[key] = val;
            }
            return val;
        }
    }
}
